package io.gitee.minelx.commontools.factor;

import java.util.List;
import java.util.OptionalInt;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static java.util.stream.IntStream.range;

public class Register {
	private final List<Integer> factors;

	private final int elementCounts;

	private Register(List<Integer> factors) {
		this.factors = factors;
		// reduce to element count
		elementCounts = reduceMultiply(factors).orElseThrow(RuntimeException::new);
	}

	public int size() {
		return elementCounts;
	}

	public List<Integer> at(int index) {
		if (index < 0 || index >= elementCounts) {
			throw new IndexOutOfBoundsException("index should be in range '[0, " + elementCounts + ").");
		}
		return range(0, factors.size())
				.mapToObj(at -> at(index, at))
				.collect(toList());
	}

	private int at(int index, int at) {
		List<Integer> remainders = factors.subList(at, factors.size());
		List<Integer> dividers = factors.subList(at + 1, factors.size());
		int remainder = reduceMultiply(remainders).orElseThrow(RuntimeException::new);
		int divider = reduceMultiply(dividers).orElse(1); // divide 1
		return index % remainder / divider;
	}

	private OptionalInt reduceMultiply(List<Integer> elements) {
		return elements.stream().mapToInt(unboxed -> unboxed).reduce(Math::multiplyExact);
	}

	public Stream<List<Integer>> all() {
		return range(0, size())
				.mapToObj(this::at);
	}

	public static Register of(List<Integer> factors) {
		if (factors.isEmpty()) {
			throw new IllegalArgumentException("no factor specified. cause the factors which passed is an empty list.");
		}
		return new Register(factors);
	}
}
